Skip to content

docs(promotion): add lesson promotion filter and promote the current batch - #21

Merged
juniperbevensee merged 5 commits into
mainfrom
dev/juniperbevensee/lesson-promotion-filter
Aug 5, 2026
Merged

docs(promotion): add lesson promotion filter and promote the current batch#21
juniperbevensee merged 5 commits into
mainfrom
dev/juniperbevensee/lesson-promotion-filter

Conversation

@juniperbevensee

@juniperbevensee juniperbevensee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

What

  • Adds docs/lesson-promotion-filter.md — four ordered tests (substitution, counterfactual-audience, independence, recurrence-or-mechanism) that decide whether an instance's hard-won lesson is general or instance-local, with worked verdicts on real material including the rejections.
  • Promotes the batch that passes, sanitised and rewritten for a general research assistant, into hermes-skill/references/: evaluation-validity.md, enforcement-ladder.md, agent-failure-modes.md, each reachable from SKILL.md at the point in the workflow where it would be violated.
  • Adds docs/deployment-reach.md — merging is not shipping; traces the four manual steps between main and a running agent and assesses POST /api/harnesses/:id/usecase/reapply as the delivery mechanism.
  • Adds tests/test_docs_integrity.py (10 tests) enforcing the mechanical half: internal links resolve, the shipped skill never links outside itself, every shipped reference is pointed at from SKILL.md, and no tracked markdown carries instance-runtime shapes. Fixes a pre-existing dead link in CONTRIBUTING.md (docs/onboarding-contributors.mddocs/onboarding.md), which is what the link test caught first.

Why

Lessons were accumulating in one instance's runtime and in org memory, and only reached the package when someone did it by hand. promotion-and-upstream.md already says how to promote but not what, and "strip the particulars, keep the method" is easy to agree with and hard to apply — the load-bearing cases are rules stated in one domain's vocabulary whose mechanism is domain-free. The filter makes that call explicit and repeatable, so the next lesson has a path up rather than needing this exercise repeated.

What was promoted, and what was not

Promoted (each rewritten domain-free; evidence reduced to derived metrics and code shapes):
an optional guard is documentation, and the available/default/gated ladder · a guard should cost a report, not a run · running a guard's tests ≠ proving anything invoked it · prove a negative check can return a positive before trusting it · your metric's ceiling is the reliability of your ground truth · report the tolerance curve, not the point · leakage has a unit (file-disjoint ≠ subject-disjoint) · if a config knob moves the metric more than the claimed effect, you are measuring configuration · the trajectory check (per-artifact validation is structurally blind to reversals) · record your data-selection threshold and show a second cut · pressure trades verification for resolution · default affirmation makes agreement uninformative · a derived value restated on a schedule becomes an observation · a mentioned date is not a deadline without set_by/type/deliverable · a stated limitation is a claim with a date on it.

Deliberately left instance-local: label-taxonomy counts and the class filter, identifier-from-filename parsing, per-tool script paths and flags, corpus/site/collaborator names, chat-platform guild/channel/bot IDs used by a scheduled job, the specific date mistaken for a deadline, and an archival corpus that identifies one unpublished study's design. Each is recorded in the filter doc with the test it failed, because the rejected list is how the next contributor calibrates.

Already promoted, so not re-promoted: "score every arm of a comparison with the same matcher." It is in docs/trustworthy-comparison.md (#18) — and the instance made that exact error again afterwards. The filter was never the bottleneck; delivery was. That finding is what produced deployment-reach.md.

Verification

  • python3 -m pytest tests/291 passed, 7 skipped (baseline on origin/main was 281 passed, 7 skipped; +10 from the new file). No existing test modified.
  • Sanitization gate: sanitization: clean (both layers ran) in CI. The semantic layer — the one that detects study particulars, and the one that matters here because this PR moves text from a private context into a public repo — ran under --require-semantic (fails closed) and returned ok for all 9 content-bearing files. Locally I could only run the deterministic layer (no key on this machine), which was also clean; the CI run is the one to trust.
  • Manual particulars audit over added lines as a stopgap, not a substitute: grepped for collaborator names, species/site/corpus names, subject IDs, recording-filename prefixes, method names, platform IDs and private paths — zero hits. It did catch one real defect: a "synthetic" test fixture had inherited a genuine recording date, now replaced with an obviously fictional one.
  • Divergence established by evidence, not assumption: rsynced the deployed skill tree off the host and diffed it against the repo in both directions. The repo has content the runtime never received (the whole "Before you report that A beats B" section); the runtime has ~30 reference files that exist in no repository. Negative results (e.g. "this section is absent from the deployed tree") were each run with a positive control first.

Risk

One finding here is operational and time-sensitive, and is the reason deployment-reach.md exists.

Re-apply is destructive at directory granularity: with overwrite set — which re-apply always sets — the install removes the destination artifact directory recursively (rm(destDir, {recursive: true, force: true})) before copying. Calling it against the instance that motivated this PR today would delete ~30 reference documents that exist in no repository anywhere, including the entire batch this PR harvests, and replace the directory with the single SKILL.md the package currently ships. The SOUL is deliberately exempted from re-seeding for exactly this reason; the carve-out has not been extended to skill directories.

So: capture the deployed artifact directory into the instance's private repo before anyone calls re-apply. deployment-reach.md lists that as C1 with the rest of the sequence.

Second, nothing in this PR reaches a running agent on merge. The template registry pins v0.4.1, which is six commits behind main; a tag must be cut and the pin bumped. Merging this changes the repository and nothing else — stated here rather than left implied.

This PR itself is documentation plus one new test file. No plugin, engine, or CI behaviour changes.

juniperbevensee and others added 5 commits August 5, 2026 00:26
…batch

Adds a documented, repeatable filter for deciding whether an instance-level
lesson is general (ships to every instance) or specific (stays local), and
promotes the batch that passes it.

Promoted lessons go to hermes-skill/references/ rather than docs/, because only
hermes-skill/, matilde_plugin/ and docker/SOUL* are installed into a deployed
agent's data directory. A rule an agent must act on that lives in docs/ never
reaches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wn fixture

The positive control for the instance-particular shape scan carried a real
guild ID from the instance this package was promoted from — in a public
repo, under a docstring asserting the strings are synthetic. The scan only
walked tracked *.md, so every test was green while the leak sat inside the
scanner itself.

- Replaced with a fabricated 1000000000000000001.
- Extended the scan to tracked tests/*.py, so it now reads the file it is
  written in.
- Added an explicit `leak-scan: synthetic` line marker for the fixtures that
  must contain the forbidden shapes, so the exemption is narrow, greppable
  and reviewable rather than a whole-file skip.

Proved red-then-green both ways: replanting the real ID fails the scan;
a real ID on any non-marked line fails it too. 291 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An audit of this PR against its own filter found instance material that
should not have travelled into a public package.

- deployment-reach.md: drop the C1-C5 next-steps table. It is one
  instance's operational backlog and fails T2 outright -- no stranger can
  act on "commit the deployed artifact directory to the instance's private
  repo". Only C2 survives, rewritten as a design recommendation about the
  mechanism (a re-apply should be non-destructive or refuse) rather than a
  task assigned to someone, with the overlay-path point kept as its
  corollary.
- deployment-reach.md: de-urgency the incident passage. "right now",
  "about thirty" documents and "six weeks" of editing are this instance's
  timeline, not a general claim. Restated as the mechanism: re-applying
  against a hand-edited runtime deletes any file the runtime holds that
  the template does not.
- agent-failure-modes.md: replace a real incident date in the rendering
  example with an obviously generic one. The illustration keeps its shape
  without carrying our timeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…omoted

The Kept local table claimed the label-taxonomy family/label counts were
withheld under T1. They were not: "41 families to 14 classes" ships in
hermes-skill/references/evaluation-validity.md as E5's evidence. The PR
was rejecting something it had promoted.

The rejection list is what calibrates the next contributor, so an
inconsistency there is worse than a missing entry -- it teaches the wrong
boundary. Reconciled honestly rather than by deleting the E5 evidence:
what stays local is the taxonomy's identity and the specific class filter;
the raw counts are derived quantities and travel with the rule under T3.
Fixed in both places the claim appears (the T1 worked example and the
Kept local row).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…enforce

The strongest candidate in this batch was not promoted. The incident sits
in docs/trustworthy-comparison.md, and docs/ is contributor-facing and not
shipped to agents -- a split this very PR adds a test for. So the rule was
at rung 1 of the package's own enforcement ladder: available, greppable,
citable, and reaching no running agent. That is exactly the recurrence
mechanism deployment-reach.md was written about, and it was happening
inside the promotion pass that documented it.

E6, written generally, in the shipped reference:

- A saved model encodes unstated assumptions about input preprocessing --
  sampling/resolution, transform, scaling, normalisation scope. Loading it
  checks none of them and nothing errors when they are violated; the model
  answers a different question with the same confidence.
- Parameters can look identical while preprocessing differs, because the
  quantity that must match is often derived (hop / sample rate = seconds
  per frame). Report derived quantities, not the constants.
- Write the preprocessing signature into the checkpoint at save time and
  assert it on load; refuse to run on mismatch. Cheap, and it converts a
  silent large error into a loud stop.
- Never retype a preprocessing function into a new script -- import the
  one training used. A reimplementation is a new program wearing an old
  name.
- A borrowed checkpoint is the high-risk case: weights you did not train
  in this script carry no guarantee about this script's preprocessing.
- An ablation whose arms differ in more than one respect does not
  attribute its delta. Say so instead of reporting it.

Evidence is bare derived metrics only (publishable under the doc's own
T3); no study, species, collaborator, script or split named. Pointer added
to SKILL.md beside the existing evaluation block so it is loaded, not
merely stored.

Also generalises E4's representation example, which read as
domain-shaped: a linear-amplitude representation where the field standard
is logarithmic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@juniperbevensee

Copy link
Copy Markdown
Collaborator Author

Audit against the governing rule, and five edits

Re-read this PR against the rule it exists to serve: this repo is the public base package every instance installs. General research-methodology rules belong here; our instance's particulars and to-do list do not. Five things needed fixing, one of them sharp.

1. Cut the instance to-do list — docs/deployment-reach.md

The "Named next steps" table (C1–C5) was our operational backlog living in a public package. It fails the PR's own T2: no stranger can act on "commit the deployed artifact directory to the instance's private repo," and "C1 is the only one that is urgent" is a statement about our week.

Removed. C2 kept, rewritten as a design recommendation about the mechanism — a template re-apply should be non-destructive, or it should refuse — rather than a task assigned to someone. The overlay-path point (old C4) survives as its corollary, because without it a non-destructive re-apply only postpones the deletion.

2. De-urgency the incident passage — docs/deployment-reach.md

"right now", "about thirty" documents, "editing for six weeks" are our timeline, not a general claim. Restated as the mechanism:

Re-applying against a hand-edited runtime deletes any file the runtime holds that the template does not — including, in the observed case, the entire batch a promotion pass was harvesting.

3. The rejection table contradicted the promotion — docs/lesson-promotion-filter.md

The sharpest finding. The Kept local table claimed the label-taxonomy family/label counts were withheld under T1. They were not — "41 families to 14 classes" ships in hermes-skill/references/evaluation-validity.md as E5's evidence. The PR was rejecting something it had promoted.

That matters more than an ordinary inconsistency: the PR itself argues the rejected list is how the next contributor calibrates. A rejection list that misreports the boundary teaches the wrong boundary, and it does so with the authority of a worked example.

Reconciled honestly rather than by deleting the E5 content. What stays local is the taxonomy's identity and the specific class filter; the raw counts are derived quantities and travel with the rule under T3. Fixed in both places the claim appeared — the T1 worked example and the Kept local row.

4. Generic date — hermes-skill/references/agent-failure-modes.md

The rendering example carried a real incident date. Replaced with an obviously generic one; the illustration keeps its shape without carrying our timeline.

5. The missing rule — E6, now promoted

The strongest candidate in this batch was not promoted. The incident is in docs/trustworthy-comparison.md — and docs/ is contributor-facing and not shipped to agents, a split this very PR adds a test for (test_docs_are_not_shipped_to_agents).

So the rule sat at rung 1 of the package's own enforcement ladder: available, greppable, citable in review, reaching no running agent. That is precisely the recurrence mechanism deployment-reach.md was written about — happening inside the promotion pass that documented it.

Added to hermes-skill/references/evaluation-validity.md as E6 — A checkpoint carries a contract it cannot enforce:

  • A saved model encodes unstated assumptions about input preprocessing — sampling/resolution, transform, scaling, normalisation scope. Loading it checks none of them, nothing errors when they are violated, and the model answers a different question with the same confidence.
  • Parameters can look identical while preprocessing differs, because the quantity that must match is often derived (hop ÷ sample rate = seconds per frame). Report derived quantities, not the constants they came from.
  • Write the preprocessing signature into the checkpoint at save time and assert it on load; refuse to run on mismatch. Cheap, and it converts a silent large error into a loud stop.
  • Never retype a preprocessing function into a new script — import the one training used. A reimplementation is a new program wearing an old name.
  • A borrowed checkpoint is the high-risk case: weights you did not train in this script carry no guarantee about this script's preprocessing, and the intuition runs the wrong way — inherited weights feel more settled than fresh ones.
  • An ablation whose arms differ in more than one respect does not attribute its delta. Say so instead of reporting the number.

Evidence is bare derived metrics only, publishable under the doc's own T3. No study, species, collaborator, script name or split ID. Pointer added to SKILL.md beside the existing evaluation block so it is loaded, not merely storedtest_skill_points_at_every_reference_it_ships still passes.

Also generalised E4's representation example, which read domain-shaped, to "a linear-amplitude representation where the field standard is logarithmic."

Verification

  • python3 -m pytest -q291 passed, 7 skipped
  • python3 scripts/check_sanitization.py --full-treeclean, deterministic layer only. The semantic layer needs a key and runs in CI; the sanitize job passed there, so the semantic layer did run against these commits.
  • Grep sweep of the full diff for species names, corpus/site labels, collaborator names, script names, split IDs, host paths, hashes, emails and 17–19-digit identifiers: no hits. The only long-digit value is the fabricated 1000000000000000001 in the leak detector's own positive control, carrying its leak-scan: synthetic marker. (One term matched on a substring — "intuition" contains "tui" — and is a false positive.)

Remaining month-level provenance markers ("Promoted from an instance, 2026-08") are kept deliberately: they date the promotion without dating an incident.

@juniperbevensee
juniperbevensee merged commit 01d27de into main Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant